<!DOCTYPE html>
<html lang="zn-ch">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>tank batle</title>
    <script
      src="https://unpkg.com/three@latest/build/three.module.js"
      async
    ></script>
    <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
    <script src="https://unpkg.com/naive-ui"></script>
    <script src="https://www.unpkg.com/html2canvas@1.4.1/dist/html2canvas.js"></script>

    <style>
      test {
        justify-content: center;
      }
      * {
        padding: 0;
        margin: 0;
      }
      body {
        position: relative;
        width: 100vw;
        height: 100vh;
        overflow: hidden;
      }
      #game {
        width: 100vw;
        height: 100vh;
      }
      #text {
        position: absolute;
        left: 0;
        top: 0;
        z-index: -1;
        /* background-color:red */
      }
      #app {
        position: absolute;
        top: 0;
        right: 0;
        z-index: 1;
        background: white;
        width: 200px;
        height: 100vh;
        display: flex;
        flex-direction: column;
        justify-content: flex-start;
        align-items: center;
      }
    </style>
  </head>
  <body>
    <div id="text"></div>
    <div id="app">
      <div style="margin-bottom: 5px">
        游戏进度<span style="color: red">{{value}}</span>
      </div>
      <p>可以暂停游戏用方向键精确控制</p>
      <div>
        <n-space style="height: 300px; justify-content: center">
          <n-slider v-model:value="value" :max="max" vertical />
        </n-space>
        <div style="margin-top: 10px">
          <n-button tertiary @click="pause=!pause" type="success" v-if="!pause">
            运行
          </n-button>
          <n-button tertiary @click="pause=!pause" type="warning" v-else>
            暂停
          </n-button>
        </div>
        <div style="margin-top: 10px">
          <n-button tertiary @click="copy" type="info">
            复制当前进度对应的请求
          </n-button>
        </div>
        <p style="margin-top: 10px">播放间隔ms（请求间间隔）</p>
        <n-input-number
          :max="150"
          :min="30"
          :step="10"
          v-model:value="speed"
          size="small"
        ></n-input-number>
        <div style="margin-top: 10px">操作：{{operate}}</div>
      </div>
    </div>
    <div id="game"></div>
    <script type="importmap">
      {
        "imports": {
          "three": "https://unpkg.com/three@latest/build/three.module.js"
        }
      }
    </script>
    <script type="module">
      import * as THREE from "three";
      const mockData = $mockData$;
      window.mockData = mockData;
      window.vue_max.value = mockData.length - 1;
      const tankTextrueMap = {
        TOP: new THREE.TextureLoader().load(
          "https://pic.imgdb.cn/item/65102950c458853aef8f0974.png"
        ),
        RIGHT: new THREE.TextureLoader().load(
          "https://pic.imgdb.cn/item/6510294fc458853aef8f0963.png"
        ),
        DOWN: new THREE.TextureLoader().load(
          "https://pic.imgdb.cn/item/6510294fc458853aef8f0950.png"
        ),
        LEFT: new THREE.TextureLoader().load(
          "https://pic.imgdb.cn/item/6510294fc458853aef8f0956.png"
        ),
      };
      const scene = new THREE.Scene();
      const camera = new THREE.PerspectiveCamera(
        75,
        window.innerWidth / window.innerHeight,
        0.1,
        1000
      );
      const tankBatle = document.querySelector("#game");
      const renderer = new THREE.WebGLRenderer({
        preserveDrawingBuffer: true,
      });
      renderer.setSize(window.innerWidth, window.innerHeight);
      const gameGroup = new THREE.Group();
      scene.add(gameGroup);
      camera.position.x = 700;
      camera.position.y = -430;
      camera.position.z = 600;
      camera.lookAt(700, -430, 0);
      tankBatle.appendChild(renderer.domElement);

      init();
      animate();
      function animate() {
        requestAnimationFrame(animate);
        renderer.render(scene, camera);
        // controls.update();
      }

      async function init() {
        const tanksGroup = await createTankGroup();
        const bulletGrop = createBulletGroup();
        const safeArea = createSafeArea();
        const props = createProp();
        gameGroup.add(tanksGroup.group);
        gameGroup.add(bulletGrop.group);
        gameGroup.add(safeArea.group);
        gameGroup.add(props.group);
        const data = mockData;

        //创建地图
        gameGroup.add(createBattleField(data[0].request.map));
        //每隔100ms渲染坦克或者子弹等位置
        let idx = 0; //渲染索引
        let timer;
        let freshTimer;
        let pause = false;
        let speed = 100;
        window.onPause = (flag) => {
          pause = flag;
          if (flag) {
            clearTimeout(timer);
          } else {
            render_();
          }
        };
        window.onchangeValueChange = (newIdx) => {
          if (newIdx !== idx - 1) {
            idx = newIdx;
            clearTimeout(freshTimer);
            freshTimer = setTimeout(() => {
              render_();
              console.log("手动更新", idx, newIdx);
            }, 1000);
          }
        };
        window.onSpeed = (speed_) => {
          speed = speed_;
        };
        render_();
        function render_() {
          clearTimeout(timer);
          if (pause) return;
          //更新坦克信息或者创建坦克
          tanksGroup.update(data[idx].request.tankSatesVoList);
          bulletGrop.update(data[idx].request.bulletSateVoList);
          safeArea.update(data[idx].request.safeAreaStateVo);
          props.update(data[idx].request.propVoList);
          window.vue_value.value = idx;
          idx++;
          if (idx === data.length) {
            alert("game over");
            return;
          }
          timer = setTimeout(render_, speed);
        }
      }
      function createBattleField(map) {
        const mapGroup = new THREE.Group();
        for (let i = 0; i < Math.floor(860 / 50); i++) {
          for (let j = 0; j < 1400 / 50; j++) {
            const pos = coordinateTransformation(
              j * 50 + 50 / 2,
              i * 50 + 50 / 2
            );
            const cube = createMapBlock("empty", pos.x, pos.y);
            mapGroup.add(cube);
          }
        }
        const typeMap = {
          B: "brick",
          I: "iron",
          T: "tree",
          W: "water",
        };
        for (let i = 0; i < map.length; i += 10) {
          const cur = [];
          if (i + 10 >= map.length) {
            break;
          }
          for (let j = 0; j < map[i].length; j += 10) {
            const x = (j / 10) * 50 + 25;
            const y = (i / 10) * 50 + 25;
            const pos = coordinateTransformation(x, y);
            if (map[i][j] in typeMap) {
              mapGroup.add(createMapBlock(typeMap[map[i][j]], pos.x, pos.y));
            } else {
            }
          }
        }
        return mapGroup;
      }

      function createTankGroup() {
        const group = new THREE.Group();
        const nameCache = new Set();

        function createLabel(name, hp, a, d) {
          const ele = document.querySelector("#text");
          const firstRow = `h:${hp} a:${a} d:${d}`;
          ele.innerHTML = `${firstRow}`;
          const firstRowLength = firstRow.split("").reduce((pre, cur) => {
            return pre + (String(cur).charCodeAt() > 255 ? 2 : 1);
          }, 0);

          return html2canvas(ele, { dpi: 1920 }).then((canvas) => {
            const map = new THREE.TextureLoader().load(canvas.toDataURL());
            const material = new THREE.MeshBasicMaterial({
              map: map,
              transparent: true,
            });
            const geometry = new THREE.PlaneGeometry(
              Math.max(firstRowLength) * 7.5,
              20
            );
            const sprite = new THREE.Mesh(geometry, material);
            sprite.name = "label";
            sprite.position.y = -10;
            return sprite;
          });
        }
        async function createTank(
          tank = {
            atk: 10,
            def: 5,
            height: 50,
            hp: 100,
            movingDirect: "TOP",
            tankId: "host",
            width: 50,
            x: 475,
            y: 275,
          }
        ) {
          const geometry = new THREE.PlaneGeometry(50, 50);
          const material = new THREE.MeshBasicMaterial({
            map: tankTextrueMap[tank.movingDirect],
            side: THREE.DoubleSide,
            color: tank.tankId == "host" ? "red" : "transparent",
            transparent: true,
          });
          const pos = coordinateTransformation(tank.x, tank.y);
          const cube = new THREE.Mesh(geometry, material);
          const label = await createLabel(
            tank.tankId,
            tank.hp,
            tank.atk,
            tank.def
          );
          cube.userData.info = [tank.hp, tank.atk, tank.def].join("-");
          cube.add(label);
          cube.name = tank.tankId;
          cube.position.x = pos.x;
          cube.position.y = pos.y;
          cube.position.z = 0;
          return cube;
        }
        return {
          async update(tanklist) {
            for (let tank of tanklist) {
              if (nameCache.has(tank.tankId)) {
                const tankObj = group.getObjectByName(tank.tankId);
                if (!tankObj) continue;
                if (tank.hp === 0) {
                  group.remove(tankObj);
                  nameCache.delete(tank.tankId);
                  continue;
                }
                tankObj.material.map.dispose();
                tankObj.material.map = tankTextrueMap[tank.movingDirect];
                const pos = coordinateTransformation(tank.x, tank.y);
                tankObj.position.x = pos.x;
                tankObj.position.y = pos.y;
                const info = [tank.hp, tank.atk, tank.def].join("-");
                if (tankObj.userData.info !== info) {
                  tankObj.clear();
                  const label = await createLabel(
                    tank.tankId,
                    tank.hp,
                    tank.atk,
                    tank.def
                  );
                  tankObj.add(label);
                  tankObj.userData.info = info;
                }
              } else if (tank.hp > 0) {
                nameCache.add(tank.tankId);
                const tank_ = await createTank(tank);
                group.add(tank_);
              }
            }
            //清理被销毁的坦克
            for (let name of nameCache) {
              if (!tanklist.find((tank) => tank.tankId === name)) {
                const tankObj = group.getObjectByName(name);
                tankObj && group.remove(tankObj);
                nameCache.delete(name);
              }
            }
          },
          group,
        };
      }
      function createBulletGroup() {
        const group = new THREE.Group();
        const nameCache = new Set();
        function create(bullet) {
          const width =
            bullet.movingDirect === "LEFT" || bullet.movingDirect === "RIGHT"
              ? 10
              : 5;
          const height =
            bullet.movingDirect === "LEFT" || bullet.movingDirect === "RIGHT"
              ? 5
              : 10;

          const geometry = new THREE.PlaneGeometry(width, height);
          const material = new THREE.MeshBasicMaterial({
            color: "red",
          });
          const cube = new THREE.Mesh(geometry, material);
          cube.name = bullet.bulletId;
          const pos = coordinateTransformation(bullet.x, bullet.y);
          cube.position.x = pos.x;
          cube.position.y = pos.y;
          cube.position.z = 0;
          return cube;
        }
        return {
          update(bulletList) {
            for (let bullet of bulletList) {
              if (nameCache.has(bullet.bulletId)) {
                const bulletObj = group.getObjectByName(bullet.bulletId);
                const pos = coordinateTransformation(bullet.x, bullet.y);
                bulletObj.position.x = pos.x;
                bulletObj.position.y = pos.y;
              } else {
                nameCache.add(bullet.bulletId);
                group.add(create(bullet));
              }
            }
            for (let name of nameCache) {
              if (!bulletList.find((bullet) => bullet.bulletId === name)) {
                const bulletObj = group.getObjectByName(name);
                bulletObj && group.remove(bulletObj);
                nameCache.delete(name);
              }
            }
          },
          group,
        };
      }
      function createSafeArea() {
        const group = new THREE.Group();
        let r = -1;
        function create(x, y, r) {
          const geometry = new THREE.RingGeometry(r, r + 1, 100);
          const material = new THREE.MeshBasicMaterial({
            color: "red",
            side: THREE.DoubleSide,
          });
          const cube = new THREE.Mesh(geometry, material);
          cube.name = "safeArea";
          cube.position.x = x;
          cube.position.y = y;
          // cube.scale.set(0.8, 0.8, 1);
          return cube;
        }
        return {
          update(safeAreaStateVo) {
            const pos = coordinateTransformation(
              safeAreaStateVo.x,
              safeAreaStateVo.y
            );
            if (r === -1) {
              group.add(create(pos.x, pos.y, safeAreaStateVo.r));
              r = safeAreaStateVo.r;
            } else if (safeAreaStateVo.r !== r) {
              const obj = group.getObjectByName("safeArea");
              if (obj) {
                obj.scale.set(safeAreaStateVo.r / r, safeAreaStateVo.r / r, 1);
                obj.position.x = pos.x;
                obj.position.y = pos.y;
              }
            }
          },
          group,
        };
      }
      function createProp() {
        const group = new THREE.Group();
        const nameCache = new Set();
        const textrueMap = {
          ATTACK: "https://pic.imgdb.cn/item/65102940c458853aef8f0399.png",
          BLOOD: "https://pic.imgdb.cn/item/65102940c458853aef8f03ab.png",
          DEFENSE: "https://pic.imgdb.cn/item/65102940c458853aef8f03c0.png",
        };
        function create(x, y, type, id) {
          const geometry = new THREE.PlaneGeometry(50, 50);
          const material = new THREE.MeshBasicMaterial({
            map: new THREE.TextureLoader().load(textrueMap[type]),
            side: THREE.DoubleSide,
            transparent: true,
          });
          const cube = new THREE.Mesh(geometry, material);
          cube.name = id;
          cube.position.x = x;
          cube.position.y = y;
          cube.position.z = 0;
          return cube;
        }
        return {
          update(propVoList) {
            const idxMap = {
              ATTACK: 0,
              BLOOD: 0,
              DEFENSE: 0,
            };
            const props = propVoList.map((propVo) => {
              const pos = coordinateTransformation(propVo.x, propVo.y);
              return {
                ...propVo,
                x: pos.x,
                y: pos.y,
                id: propVo.propTypeEnums + "_" + idxMap[propVo.propTypeEnums]++,
              };
            });

            for (let propVo of props) {
              if (nameCache.has(propVo.id)) {
                const propObj = group.getObjectByName(propVo.id);
                propObj.position.x = propVo.x;
                propObj.position.y = propVo.y;
              } else {
                nameCache.add(propVo.id);
                group.add(
                  create(propVo.x, propVo.y, propVo.propTypeEnums, propVo.id)
                );
              }
            }
            for (let name of nameCache) {
              if (!props.find((propVo) => propVo.id === name)) {
                const propObj = group.getObjectByName(name);
                propObj && group.remove(propObj);
                nameCache.delete(name);
              }
            }
          },
          group,
        };
      }
      function createMapBlock(type, x, y) {
        const textrueMap = {
          iron: "https://pic.imgdb.cn/item/65102940c458853aef8f03ea.png",
          empty: "https://pic.imgdb.cn/item/65102940c458853aef8f03d6.png",
          tree: "https://pic.imgdb.cn/item/65102950c458853aef8f0988.png",
          water: "https://pic.imgdb.cn/item/651029c3c458853aef8f5993.png",
          brick: "https://pic.imgdb.cn/item/65102935c458853aef8eff8f.png",
        };
        const geometry = new THREE.PlaneGeometry(50, 50);
        const material = new THREE.MeshBasicMaterial({
          map: new THREE.TextureLoader().load(textrueMap[type]),
          side: THREE.DoubleSide,
        });
        const cube = new THREE.Mesh(geometry, material);
        cube.position.x = x;
        cube.position.y = y;
        cube.position.z = 0;
        return cube;
      }

      function coordinateTransformation(x, y) {
        return new THREE.Vector2(x, -y);
      }
    </script>
    <script>
      const { createApp, ref, watch, computed } = Vue;

      const app = createApp({
        setup() {
          const value = ref(0);
          const max = ref(100);
          const pause = ref(false);
          const speed = ref(100);
          const operate = ref({});
          window.vue_max = max;
          window.vue_value = value;
          watch(value, (val) => {
            operate.value = window.mockData?.[value.value]?.response;
            window.onchangeValueChange(val);
          });
          watch(pause, (val) => {
            window.onPause(val);
          });
          watch(speed, (val) => {
            window.onSpeed(val);
          });

          function copy() {
            var textArea = document.createElement("textarea");
            const data = window.mockData[value.value].request;
            data.map = window.mockData[0].request.map;
            const str = JSON.stringify(data || "");
            textArea.value = str;
            document.body.appendChild(textArea);
            textArea.select();
            document.execCommand("copy");
            document.body.removeChild(textArea);
            alert("已复制进度" + value.value + "时的请求参数");
          }
          return {
            value,
            max,
            pause,
            speed,
            operate,
            copy,
          };
        },
      })
        .use(naive)
        .mount("#app");
    </script>
  </body>
</html>
